package cloud.health.common.file.domain;

import cloud.health.common.file.infrastructure.R;
import cn.hutool.http.HttpStatus;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.google.common.primitives.Bytes;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.RandomAccessFile;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
@Component
public class BinaryFile extends AbstractFile {

    private static final int CONTAINER = 1024 * 1024;

    protected static String dir;

    protected MessageDigest md;

    @Autowired
    private void init(@Value("${file.upload.dir://home/weizuxiao/下载}") String dir) {
        BinaryFile.dir = dir;
    }


    private RandomAccessFile file;
    private boolean success = true;
    private String error = "";
    private String md5 = "";

    private static String path(String s) {
        String p = "/api/v0(/.*?$)";
        Matcher m = Pattern.compile(p).matcher(s);
        if (m.find()) {
            return m.group(1);
        }
        return null;
    }

    private static void mkdir(String path) {
        if (StringUtils.isBlank(path)) {
            return;
        }
        String p = path.substring(0, path.lastIndexOf("/"));
        File file = new File(dir + p);
        if (!file.exists()) {
            file.mkdir();
        }
        File f = new File(dir + path);
        if (f.exists()) {
            f.delete();
        }
    }

    public static BinaryFile create(Request request) {
        String path = path(request.getHttp().getUri());
        BinaryFile f = new BinaryFile();
        if (!StringUtils.isBlank(path)) {
            try {
                mkdir(path);
                f.file = new RandomAccessFile(dir + path, "rws");
                f.setPath(path);
                f.md = MessageDigest.getInstance("MD5");// MessageDigest.getInstance("SHA-256");
            } catch (Exception e) {
                f.success = false;
                f.error = "非法存储";
                log.error("创建文件失败 -->", e);
            }
        } else {
            f.success = false;
            f.error = "无效请求";
        }
        return f;
    }

    public static AbstractFile findFile(Request request) {
        String path = path(request.getHttp().getUri());
        BinaryFile f = new BinaryFile();
        if (!StringUtils.isBlank(path)) {
            try {
                f.file = new RandomAccessFile(dir + path, "r");
                f.setPath(path);
            } catch (Exception e) {
                f.success = false;
                f.error = "文件不存在";
                log.error("访问文件失败 -->", e);
            }
        } else {
            f.success = false;
            f.error = "无效请求";
        }
        return f;
    }

    @Override
    public void writeAndFlush(Request request) {
        byte[] bytes = request.fetch();
        if (!Objects.isNull(this.file) && Objects.nonNull(bytes)) {
            try {
                this.file.seek(this.file.getChannel().size());
                this.file.write(bytes);
                this.setSize(this.file.getChannel().size());
                this.md.update(bytes);
                if (request.completed()) {
                    byte[] bb = md.digest();
                    this.md5 = byte2hexLower(bb);
                }
            } catch (Exception e) {
                this.success = false;
                this.error = "文件异常";
                log.error("文件写入异常 -->", e);
            }
        }
    }

    @SneakyThrows
    @Override
    public void close() {
        if (Objects.nonNull(this.file)) {
            this.file.close();
        }
    }

    @Override
    public void download(ChannelHandlerContext context) {
        try {
            long size = this.file.getChannel().size();
            StringJoiner sj = new StringJoiner("\r\n");
            sj.add("HTTP/1.1 200 OK")
                    //.add("transfer-encoding: chunked")
                    .add("Content-Type: text/plain;charset=utf-8")
                    .add("Accept-Encoding: gzip, deflate, br")
                    .add("Content-Length:" + size)
                    .add("")
                    .add("");
            context.writeAndFlush(Unpooled.wrappedBuffer(sj.toString().getBytes()));

            long s = System.currentTimeMillis();
            long seek = 0L;
            byte[] bytes = new byte[CONTAINER];
            while(seek < size) {
                this.file.seek(seek);
                if (seek + CONTAINER >= size) {
                    bytes = new byte[(int)(size - seek)];
                }
                this.file.read(bytes);
                // context.writeAndFlush(bytes.length);
                context.writeAndFlush(Unpooled.wrappedBuffer(bytes));
                seek += CONTAINER;
                if (seek >= size) {
                    seek = size;
                }
            }
            log.info("seek = {}, cost = {}ms", seek, System.currentTimeMillis() - s);
            this.file.seek(0L);
            /*
             byte[] bytes = new byte[size];
             this.file.read(bytes);
             context.writeAndFlush(Unpooled.wrappedBuffer(this.file.));
             */
        } catch (Exception e) {
            log.error("读取文件失败 -->", e);
            R r = R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "读取文件失败");
            context.writeAndFlush(Unpooled.wrappedBuffer(r.response()));
        }
    }

    @JSONField(serialize = false)
    @Override
    public boolean isSuccess() {
        return this.success;
    }

    public String getMd5() {
        return md5;
    }

    @Override
    public String error() {
        return this.error;
    }

    private static String byte2hexLower(byte[] b) {
        String hs = "";
        String stmp = "";
        for (int i = 0; i < b.length; i++) {
            stmp = Long.toHexString(b[i] & 0XFF);
            if (stmp.length() == 1) {
                hs = hs + "0" + stmp;
            } else {
                hs = hs + stmp;
            }
        }
        return hs;
    }

}
